package gis.lifay.sdk.jts;

import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;

import java.util.List;
import java.util.Map;

/**
 * JTS 几何模型
 * 几何类型：点( POINT )、多点( MULTIPOINT )
 *           线( LINESTRING )、多线（MULTILINESTRING ）
 *           面（POLYGON ）、多面（MULTIPOLYGON ）
 *           圆
 * 创建方式：几何工厂 和 WKT
 *@Author lifay
 *@Date 2021/11/6 8:18
 **/
public class JTSGeometryUtil {

    // 几何工厂对象
    private static GeometryFactory geometryFactory = new GeometryFactory();

    /**
     * 获取点
     * @param lng 经度
     * @param lat 纬度
     * */
    public static Point getPoint(Double lng, Double lat) {
        Coordinate coordinate = new Coordinate(lng, lat);
        return geometryFactory.createPoint(coordinate);
    }

    /**
     *  获取点，通过 WKT 创建
     * @param lng 经度
     * @param lat 纬度
     * */
    public static Point getPointByWKT(Double lng, Double lat) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        // POINT(111 22)
        wktSB.append("POINT").append("(").append(lng).append(" ").append(lat).append(")");
        return (Point) createGeometry(wktSB.toString());
    }

    /**
     * 获取多点
     * @param coordinates 坐标集合
     * */
    public static MultiPoint getMultiPoint(Coordinate[] coordinates) {
        return geometryFactory.createMultiPointFromCoords(coordinates);
    }

    /**
     * 获取多点，通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public static MultiPoint getMultiPointByWKT(Coordinate[] coordinates) throws ParseException {
        // MULTIPOINT(111 22 ,111 22)
        String wktStr = createWktString("MULTIPOINT", coordinates);
        return (MultiPoint) createGeometry(wktStr);
    }

    /**
     * 获取线
     * @param coordinates 坐标集合
     * */
    public static LineString getLineString(Coordinate[] coordinates) {
        return geometryFactory.createLineString(coordinates);
    }

    /**
     * 获取线，通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public static LineString getLineStringByWKT(Coordinate[] coordinates) throws ParseException {
        String wktStr = createWktString("LINESTRING", coordinates);
        return (LineString) createGeometry(wktStr);
    }

    /**
     * 获取多线
     * @param coordList 多个坐标集合
     * */
    public static MultiLineString getMultiLineString(List<Coordinate[]> coordList) {
        LineString[] lineStrings = new LineString[coordList.size()];
        for (int m = 0; m < coordList.size(); m++) {
            lineStrings[m] = getLineString(coordList.get(m));
        }
        return geometryFactory.createMultiLineString(lineStrings);
    }

    /**
     * 获取多线，通过 WKT 创建
     * @param coordList 多个坐标集合
     * */
    public static MultiLineString getMultiLineStringByWKT(List<Coordinate[]> coordList) throws ParseException {
        String wktStr = createMultiWktString("MULTILINESTRING", coordList);
        return (MultiLineString) createGeometry(wktStr);
    }

    /**
     * 获取面
     * @param coordinates 坐标集合
     * */
    public static Polygon getPolygon(Coordinate[] coordinates) {
        return geometryFactory.createPolygon(coordinates);
    }

    /**
     * 获面
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public static Polygon getPolygon(Coordinate[] shellCoordinates, List<Coordinate[]> holeCoordList) {
        LinearRing shell = geometryFactory.createLinearRing(shellCoordinates);
        LinearRing[] holes = new LinearRing[holeCoordList.size()];
        for (int m = 0; m < holeCoordList.size(); m++) {
            Coordinate[] holeCoordinates = holeCoordList.get(m);
            holes[m] = geometryFactory.createLinearRing(holeCoordinates);
        }
        return geometryFactory.createPolygon(shell, holes);
    }

    /**
     * 获取面，通过 WKT 创建
     * @param coordinates 坐标集合
     * */
    public static Polygon getPolygonByWKT(Coordinate[] coordinates) throws ParseException {
        String wktStr = createWktStringWithPolygon("POLYGON", coordinates, null);
        return (Polygon) createGeometry(wktStr);
    }

    /**
     * 获取面，通过 WKT 创建
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public static Polygon getPolygonByWKT(Coordinate[] shellCoordinates, List<Coordinate[]> holeCoordList) throws ParseException {
        String wktStr = createWktStringWithPolygon("POLYGON", shellCoordinates, holeCoordList);
        return (Polygon) createGeometry(wktStr);
    }

    /**
     * 获取多面
     * @param coordList 多个坐标集合
     * */
    public static MultiPolygon getMultiPolygon(List<Coordinate[]> coordList) {
        Polygon[] polygons = new Polygon[coordList.size()];
        for (int i = 0; i < coordList.size(); i++) {
            polygons[i] = geometryFactory.createPolygon(coordList.get(i));
        }
        return geometryFactory.createMultiPolygon(polygons);
    }

    /**
     * 获多面
     * @param coordinateMap 面边界和孔洞坐标集合
     * */
    public static MultiPolygon getMultiPolygon(Map<Coordinate[], List<Coordinate[]>> coordinateMap) {
        Polygon[] polygons = new Polygon[coordinateMap.size()];
        int index = 0;
        for (Map.Entry<Coordinate[], List<Coordinate[]>> item : coordinateMap.entrySet()) {
            LinearRing shell = geometryFactory.createLinearRing(item.getKey()); // 面边界
            LinearRing[] holes = new LinearRing[item.getValue().size()]; // 孔洞
            for (int m = 0; m < item.getValue().size(); m++) {
                Coordinate[] holeCoordinates = item.getValue().get(m);
                holes[m] = geometryFactory.createLinearRing(holeCoordinates);
            }
            polygons[index] = geometryFactory.createPolygon(shell, holes);
            index++;
        }
        return geometryFactory.createMultiPolygon(polygons);
    }

    /**
     * 获取多面
     * @param coordList 多个坐标集合
     * */
    public static MultiPolygon getMultiPolygonByWKT(List<Coordinate[]> coordList) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append("MULTIPOLYGON").append("(");
        int index = 0;
        for (Coordinate[] coordinates : coordList) {
            if (index > 0) {
                wktSB.append(",");
            }
            // 1. 处理面边界
            wktSB.append("(");
            wktSB.append("("); // 面边界start
            wktSB.append(coordinatesToWktStr(coordinates));
            wktSB.append(")"); // 面边界end
            wktSB.append(")");
            index++;
        }
        wktSB.append(")");
        return (MultiPolygon) createGeometry(wktSB.toString());
    }

    /**
     * 获多面
     * @param coordinateMap 面边界和孔洞坐标集合
     * */
    public static MultiPolygon getMultiPolygonByWKT(Map<Coordinate[], List<Coordinate[]>> coordinateMap) throws ParseException {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append("MULTIPOLYGON").append("(");
        int index = 0;
        for (Map.Entry<Coordinate[], List<Coordinate[]>> item : coordinateMap.entrySet()) {
            Coordinate[] shellCoordinates = item.getKey();
            List<Coordinate[]> holeCoordList = item.getValue();
            if (index > 0) {
                wktSB.append(",");
            }
            wktSB.append("(");
            wktSB.append(coordinatesToWktStr(shellCoordinates, holeCoordList));
            wktSB.append(")");
            index++;
        }
        wktSB.append(")");
        return (MultiPolygon) createGeometry(wktSB.toString());
    }

    /**
     * 获取圆（无孔洞）
     * @param lng 经度
     * @param lat 纬度
     * @param radius 半径
     * */
    public static Polygon getCircle(double lng, double lat, final double radius) {
        final int arcs = 32; // 弧线点数量
        Coordinate coords[] = new Coordinate[arcs + 1];
        for (int i = 0; i < arcs; i++) { // 角度转弧度计算
            double angle = ((double) i / (double) arcs) * Math.PI * 2.0;
            double dx = Math.cos(angle) * radius;
            double dy = Math.sin(angle) * radius;
            coords[i] = new Coordinate((double) lng + dx, (double) lat + dy);
        }
        coords[arcs] = coords[0]; // 将第一个数据放在最后，形成闭环
        LinearRing ring = geometryFactory.createLinearRing(coords); // 圆环线
        Polygon polygon = geometryFactory.createPolygon(ring, null);
        return polygon;
    }

    /**
     * 通过 WKT 字符串创建几何对象
     * @param wktStr wkt 字符串
     * */
    public static Geometry createGeometry(String wktStr) throws ParseException {
        WKTReader wktReader = new WKTReader(geometryFactory);
        return wktReader.read(wktStr);
    }

    /**
     * 根据几何类型和坐标集合，创建 WKT 字符串
     * @param geomType 几何类型
     * @param coordinates 坐标集合
     * */
    public static String createWktString(String geomType, Coordinate[] coordinates) {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        wktSB.append(coordinatesToWktStr(coordinates));
        wktSB.append(")");
        return wktSB.toString();
    }

    /**
     * 将坐标集合转换为 wkt 需要的字符串
     * @param coordinates 坐标集合
     * */
    private static String coordinatesToWktStr(Coordinate[] coordinates) {
        StringBuilder wktSB = new StringBuilder();
        for (int i = 0; i < coordinates.length; i++) {
            Coordinate coordinate = coordinates[i];
            wktSB.append(coordinate.getX()).append(" ").append(coordinate.getY());
            if (coordinates.length - 1 != i) {
                wktSB.append(",");
            } // 最后一个坐标不需要加逗号
        }
        return wktSB.toString();
    }

    /**
     * 将坐标集合转换为 wkt 需要的字符串
     * */
    private static String coordinatesToWktStr(Coordinate[] shellCoordinates, List<Coordinate[]> holeCoordList) {
        StringBuilder wktSB = new StringBuilder();
        // 1. 处理面边界
        wktSB.append("("); // 面边界start
        wktSB.append(coordinatesToWktStr(shellCoordinates));
        wktSB.append(")"); // 面边界end
        // 2. 处理多个孔洞
        if (holeCoordList != null && holeCoordList.size() > 0) {
            for (int n = 0; n < holeCoordList.size(); n++) {
                Coordinate[] holeCoordinates = holeCoordList.get(n);
                wktSB.append(",");
                wktSB.append("("); // 面边界start
                wktSB.append(coordinatesToWktStr(holeCoordinates));
                wktSB.append(")"); // 面边界end
            }
        }
        return wktSB.toString();
    }

    /**
     * 根据几何类型和坐标集合，创建 WKT 字符串（多个几何图形）
     * @param geomType 几何类型
     * @param coordList 多个坐标集合
     * */
    public static String createMultiWktString(String geomType, List<Coordinate[]> coordList) {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        for (int m = 0; m < coordList.size(); m++) {
            Coordinate[] coordinates = coordList.get(m);
            wktSB.append("(");
            for (int n = 0; n < coordList.size(); n++) {
                Coordinate coordinate = coordinates[n];
                wktSB.append(coordinate.getX()).append(" ").append(coordinate.getY());
                if (coordinates.length - 1 != n) {
                    wktSB.append(",");
                } // 最后一个坐标不需要加逗号
            }
            wktSB.append(")");
            if (coordList.size() - 1 != m) {
                wktSB.append(",");
            } // 最后一个坐标不需要加逗号
        }
        wktSB.append(")");
        return wktSB.toString();
    }

    /**
     * 根据几何类型和坐标集合，创建 WKT 字符串
     * @param geomType 几何类型
     * @param shellCoordinates 面边界坐标集合
     * @param holeCoordList 多个孔洞坐标集合
     * */
    public static String createWktStringWithPolygon(String geomType, Coordinate[] shellCoordinates, List<Coordinate[]> holeCoordList) {
        StringBuilder wktSB = new StringBuilder();
        wktSB.append(geomType).append("(");
        wktSB.append(coordinatesToWktStr(shellCoordinates, holeCoordList));
        wktSB.append(")");
        return wktSB.toString();
    }

}
